using System;
using System.Collections.Generic;
using System.Threading;

using TrueCode.Core.Aspects;

namespace TrueCode.Core.Threading
{
	public class PublicPlace
	{
		private readonly	Object				_visitorsCountAndEventAccess	= new Object( );
		private readonly	List< Permit >		_visitorPermits					= new List< Permit >( );
		private				ManualResetEvent	_roomBecomeEmptyEvent;

		private readonly	Object	_isClosedAcces	= new Object( );
		private				Boolean _isClosed = true;


		public PublicPlace() : this( true ){}

		public PublicPlace( Boolean isOpened )
		{
			_isClosed = ! isOpened;
		}

		public Permit TryGetPermit( )
		{
			Permit permit = null;
			lock( _isClosedAcces )
			{
				if( !_isClosed )
				{
					lock( _visitorsCountAndEventAccess )
					{
						permit = new Permit( this );
						_visitorPermits.Add( permit );
					}
				}
			}
			return permit;
		}

		public void Leave( [NotNull] Permit permit )
		{
			lock( _visitorsCountAndEventAccess )
			{
				if( ! _visitorPermits.Remove( permit ) )
				{
					throw new Exception< PublicPlace, ArgumentException >( new ArgumentException( "This permit has not been issued by this public place or visitor with this permit has already left it.", "permit" ) );
				}

				if( _visitorPermits.Count == 0 && _roomBecomeEmptyEvent != null )
				{
					_roomBecomeEmptyEvent.Set( );
				}
			}
		}

		public void Open()
		{
			lock( _isClosedAcces )
			{
				_isClosed = false;
			}
		}

		public void Close()
		{
			lock( _isClosedAcces )
			{
				_isClosed = true;
			}
		}

		public void WaitIsEmpty()
		{
			lock( _visitorsCountAndEventAccess )
			{
				if( _visitorPermits.Count > 0 )
				{
					_roomBecomeEmptyEvent = new ManualResetEvent( false );
				}
			}

			if( _roomBecomeEmptyEvent != null )
			{
				_roomBecomeEmptyEvent.WaitOne();
				_roomBecomeEmptyEvent.Close( );
				_roomBecomeEmptyEvent = null;
			}
		}

		public void CloseAndWaitIsEmpty()
		{
			Close();
			WaitIsEmpty();
		}

		public class Permit : IDisposable
		{
			private readonly PublicPlace _place;
			private Int32 _isDisposed;

			public Permit( PublicPlace place )
			{
				_place = place;
			}

			public void Dispose( )
			{
				if( Interlocked.Exchange( ref _isDisposed, 1 ) != 1 )
				{
					_place.Leave( this );
				}
			}
		}
	}
}
